<?php

namespace Happy\CommandsService;

require __DIR__.'/../vendor/autoload.php';
require __DIR__.'/Database/config.php';

use Happy\CommandsService\Constants\ConnectionsConstants;
use Happy\CommandsService\Constants\SessionsConstants;
use Happy\CommandsService\Database\Models\CommandsModel;
use Happy\CommandsService\Database\Models\DockerModel;
use Happy\CommandsService\Database\Models\ResponseModel;
use Happy\CommandsService\Database\Models\ServerModel;
use Happy\CommandsService\Database\Models\ServiceModel;
use Happy\CommandsService\Database\Models\SessionsModel;
use Happy\CommandsService\Parsers\CommandResponseParser;
use Happy\CommandsService\Parsers\ResponseObject;
use Happy\CommandsService\RabbitMQ\RabbitMqManager;
use Happy\CommandsService\RabbitMQ\RabbitMqReciever;
use Yaroslam\SSH2\Session\AbstractSession;
use Yaroslam\SSH2\Session\ChainSession;
use Yaroslam\SSH2\Session\Connection\ConnectionInterface;
use Yaroslam\SSH2\Session\Connection\UserPasswordConnection;
use Yaroslam\SSH2\Session\Session;

class CommandService
{
    public $manager;

    public $reciever;

    public $sessions;

    private $sessionConnectionMapper;

    private $sessionTypesMapper;

    private $sessionApplyMapper;

    public function __construct($queue)
    {

        $this->manager = RabbitMqManager::getInstance('194.87.110.114', 5672, 'guest', 'guest');
        $this->reciever = new RabbitMqReciever('194.87.110.114', 5672, 'guest', 'guest', $this->manager);

        $this->sessions = [];
        $this->sessionConnectionMapper = [
            ConnectionsConstants::USER_PASSWORD_CONNECTION => UserPasswordConnection::class,
        ];

        $this->sessionTypesMapper = [
            SessionsConstants::DEFAULT_SESSION => Session::class,
            SessionsConstants::CHAIN_SESSION => ChainSession::class,
        ];

        $this->sessionApplyMapper = [
            SessionsConstants::DEFAULT_SESSION => function (Session $session, string $command) {
                return $session->exec($command)->apply();
            },
            SessionsConstants::CHAIN_SESSION => function (ChainSession $session, string $command) {
                $chainRes = $session->initChain()->exec($command)->apply()->getExecContext();
                $session->resetSession();

                return $chainRes;
            },
        ];

        $this->reciever->changeQueue($queue);
        $this->reciever->listen([$this, $queue]);
    }

    private function openSession($userId, $connectionName, string $type, $parameters)
    {
        $connection = $this->createConnection($connectionName);
        $session = $this->createSession($connection, $parameters, $type);
        $this->addSession($userId, $connectionName, $session, $type);
    }

    private function getSession($userId, $connectionName, string $type, $parameters)
    {
        if (empty($this->sessions[$userId][$connectionName][$type])) {
            $this->openSession($userId, $connectionName, $type, $parameters);
        }

        return $this->sessions[$userId][$connectionName][$type];
    }

    private function addSession(int $userId, string $connectionName, AbstractSession $session, string $type): void
    {
        if (empty($this->sessions[$userId][$connectionName][$type])) {
            $this->sessions[$userId][$connectionName][$type] = $session;
        }
    }

    private function createConnection(string $connectionName): ConnectionInterface
    {
        return new $this->sessionConnectionMapper[$connectionName]();
    }

    private function createSession(ConnectionInterface $connection, array $parameters,
        string $sessionType = SessionsConstants::DEFAULT_SESSION): AbstractSession
    {
        return new $this->sessionTypesMapper[$sessionType]($connection, $parameters);
    }

    public function interact($msg)
    {
        var_dump(json_decode($msg->body, true));
        $msg = json_decode($msg->body, true);
        $session = $this->getSession($msg['user'], $msg['connection'], $msg['sessionType'], $msg['parameters']);
        $commandRes = $this->sessionApplyMapper[$msg['sessionType']]($session, $msg['command']);

        /** @var ResponseObject $response */
        $parser = new CommandResponseParser();
        $response = $parser->parseResponse(SessionsConstants::DEFAULT_SESSION, $commandRes);
        $session = SessionsModel::query()
            ->where([
                ['user_id', '=', $msg['user']],
                ['server', '=', '194.87.110.114'],
                ['connection_id', '=', 1],
                ['active', '=', 1],
            ])
            ->first();

        $responseM = new ResponseModel();
        $responseM->code = $response->code;
        $responseM->response = $response->response;
        $responseM->success = $response->success;
        $responseM->save();

        $command = new CommandsModel();
        $command->time = time();
        $command->command = $msg['command'];
        $command->session()->associate($session);
        $command->response()->associate($responseM);
        $command->save();
    }

    public function watchers($msg)
    {
        $msg = json_decode($msg->body, true);
        $session = $this->getSession($msg['user'], $msg['connection'], $msg['sessionType'], $msg['parameters']);
        $commandRes = $this->sessionApplyMapper[$msg['sessionType']]($session, $msg['command']);

        /** @var ResponseObject $response */
        $parser = new CommandResponseParser();
        $response = $parser->parseResponse(SessionsConstants::DEFAULT_SESSION, $commandRes);
        $session = SessionsModel::query()
            ->where([
                ['user_id', '=', $msg['user']],
                ['server', '=', '194.87.110.114'],
                ['connection_id', '=', 1],
                ['active', '=', 1],
            ])
            ->first();

        $responseM = new ResponseModel();
        $responseM->code = $response->code;
        $responseM->response = $response->response;
        $responseM->success = $response->success;
        $responseM->save();

        $command = new CommandsModel();
        $command->time = time();
        $command->command = $msg['command'];
        $command->session()->associate($session);
        $command->response()->associate($responseM);
        $command->save();

        /** @var ServerModel $server */
        $server = ServerModel::query()->where('ip_address', '=', '194.87.110.114')->first();
        /** @var ServiceModel $service */
        $service = ServiceModel::query()->where('service_check_command', '=', $msg['command'])->first();
        $server->services()->detach($service->id);
        if ($response->success) {
            $server->services()->attach($service->id);
        }
        $server->save();
    }

    public function services($msg)
    {
        $msg = json_decode($msg->body, true);
        $session = $this->getSession($msg['user'], $msg['connection'], $msg['sessionType'], $msg['parameters']);
        $commandRes = $this->sessionApplyMapper[$msg['sessionType']]($session, $msg['command']);

        /** @var ResponseObject $response */
        $parser = new CommandResponseParser();
        $response = $parser->parseResponse(SessionsConstants::DEFAULT_SESSION, $commandRes);
        $session = SessionsModel::query()
            ->where([
                ['user_id', '=', $msg['user']],
                ['server', '=', '194.87.110.114'],
                ['connection_id', '=', 1],
                ['active', '=', 1],
            ])
            ->first();

        $responseM = new ResponseModel();
        $responseM->code = $response->code;
        $responseM->response = $response->response;
        $responseM->success = $response->success;
        $responseM->save();

        $command = new CommandsModel();
        $command->time = time();
        $command->command = $msg['command'];
        $command->session()->associate($session);
        $command->response()->associate($responseM);
        $command->save();

        /** @var ServerModel $server */
        $server = ServerModel::query()->where('ip_address', '=', '194.87.110.114')->first();
        DockerModel::query()->where('server', '=', '194.87.110.114')->delete();
        /** @var DockerModel $service */
        $docker = new DockerModel();
        $docker->server = '194.87.110.114';
        $docker->containers = $response->response;
        $docker->save();
    }
}
